﻿package  {
	import flash.display.MovieClip;
	import flash.events.ContextMenuEvent;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.ui.Keyboard;
	import flash.utils.getDefinitionByName;
	import flash.utils.getTimer;
	import flash.utils.Timer;
	import flash.events.TimerEvent;
	
	public class Main extends MovieClip
	{
		static const levelHeight = 480; 
		static const gravity:Number = .004;
		
		public var condicao:Boolean = false;
		public var deslocamento:Number = 0;
		public var velocidade = -3;
		public var player:Player;             // Jogador
		public var playerMask:PlayerMask;     // Sprites do jogador
		public var platformObjects:Array;     // Array das plataformas do level
		public var wallObjects:Array;         // Array das paredes do level
		public var solidObjects:Array;        // Array de objetos que podem sofrer colisao
		public var prizeObjects:Array;        // Itens que o jogador pode adquirir
		public var movingObjects:Array;
		public var movingPlataforms:Array;
		public var blueFaerie:FadaAzul;
		public var yellowFaerie:FadaAmarela;
		
		public var boardSelection:Array;
		public var puzzlePieces:Array;
		public var fireArray:Array;
		public var currentPiece:Piece;
		
		public var levelNumber:int;           // Level em que o jogador se encontra
		public var gameStateString:String;    // Nome da tela em que o jogo se encontra
		public var gameState:MovieClip;       // Movieclip que está sendo exibida no momento
		
		public var publicTimer:Timer;
		public var lastTime:Number;           // Tempo de cada iteração
		
		public var edgeDistance:int = 200;    // Distancia que a tela começa a se mover
		
		public function Main() 
		{
			init();
		}
		
		public function init():void
		{
			gameStateString = "Menu";
			levelNumber = 1;
			lastTime = 0;
			
			selectGameState();
		}
		
		public function selectGameState():void
		{
			if (gameStateString == "Menu")
			{
				gameState = new GameMenu();
				gameMenu();
			}
			else if (gameStateString == "Introduction")
			{
				gameState = new Introduction();
				gameIntroduction();
			}
			else if (gameStateString == "GameLevel")
			{
				startLevel();
			}
			else if (gameStateString == "Puzzle")
			{
				startPuzzle();
			}
			else if (gameStateString == "GameOver")
			{
				gameState = new GameOver();
				gameOver();
			}
		}
		
		public function gameMenu():void
		{
			addChild(gameState);
			stage.addEventListener(KeyboardEvent.KEY_DOWN, checkSpace, false, 0, true);
		}
		
		public function checkSpace(e:KeyboardEvent):void
		{
			if (e.keyCode == Keyboard.SPACE)
			{
				stage.removeEventListener(KeyboardEvent.KEY_DOWN, checkSpace);
				removeChild(gameState);
				gameIntroduction();
			}
		}
		
		public function gameIntroduction():void
		{
			gameState = new Introduction();
			addChild(gameState);
			
			stage.addEventListener(Event.ENTER_FRAME, scrollIntro, false, 0, true);
			stage.addEventListener(KeyboardEvent.KEY_DOWN, secretButton, false, 0, true);
		}
		
		public function secretButton(e:KeyboardEvent):void
		{
			if (e.keyCode == Keyboard.CAPS_LOCK)
			{
				stage.removeEventListener(KeyboardEvent.KEY_DOWN, secretButton);
				startLevel();
			}
		}
		
		public function scrollIntro(e:Event):void
		{
			gameState.x -= 0.5;
			
			if (gameState.x < -(gameState.width-stage.stageWidth))
			{
				startLevel();
			}
		}
		
		public function startLevel():void
		{
			removeChild(gameState);
			stage.removeEventListener(Event.ENTER_FRAME, scrollIntro);
			clearContainers();
			addLevelEvents();
			
			
			selectLevel();
			checkLevelObjects();
			
			player = new Player(150, 425);
			playerMask = new PlayerMask(150, 425);
			yellowFaerie = new FadaAmarela();
			blueFaerie = new FadaAzul(1311.25,246);
			
			
			gameState.addChild(blueFaerie);
			gameState.addChild(player);
			gameState.addChild(playerMask);
			gameState.addChild(yellowFaerie);
			
			addLevelEvents();
		}
		
		public function puzzleTutorial():void
		{
			clearContainers();
			removeLevelEvents();
			removeChild(gameState);
			gameState = new PuzzleTutorial();
			stage.addEventListener(KeyboardEvent.KEY_DOWN, goToPuzzle, false, 0, true);
			addChild(gameState);
		}
		
		public function goToPuzzle(e:KeyboardEvent):void
		{
			if(e.keyCode == Keyboard.SPACE)
			{
				removeEventListener(KeyboardEvent.KEY_DOWN,goToPuzzle);
				removeChild(gameState);
				startPuzzle();
			}
		}
		
		
		public function startPuzzle():void
		{
			clearContainers();
			currentPiece = null;
			stage.addEventListener(KeyboardEvent.KEY_DOWN, restartPuzzle, false, 0, true);
			selectLevel("Puzzle");
			
			createBoard();
		}
		
		public function restartPuzzle(e:KeyboardEvent):void
		{
			if (e.keyCode == Keyboard.SPACE)
			{
				removeChild(gameState);
				startPuzzle();
			}
		}
		
		public function createBoard():void
		{
			var board:Array = Piece.puzzles[levelNumber - 1];
			var boardSize:int = 8;
			var posX:Number;
			var posY:Number;
			
			for (var i:int = 0; i < boardSize; i++)
			{
				puzzlePieces[i] = new Array();
				boardSelection[i] = new Array();
				
				for (var j:int = 0; j < boardSize; j++)
				{
					posX = 160 + Piece.pieceWidth / 2 + (Piece.pieceWidth * j) + (j + 1) * 2;
					posY = 140 + Piece.pieceHeight / 2 + (Piece.pieceHeight * i) + (i) * 2;
					puzzlePieces[i].push(new Piece(board[i][j], posX, posY, i, j));
					gameState.addChild(puzzlePieces[i][j]);
					puzzlePieces[i][j].addEventListener(MouseEvent.CLICK, selectPiece, false, 0, true);
					
					boardSelection[i][j] = new Selection("unselected", i, j);
					gameState.addChild(boardSelection[i][j]);
				}
			}
		}
		
		public function selectPiece(e:MouseEvent):void
		{
			if (currentPiece != null)
			{
				clearBoardSelection();
				
				if (currentPiece == e.target)
				{
					currentPiece = null;
				}
				else
				{
					currentPiece = e.target as Piece;
					
					if (currentPiece.currentLabel != 'rock')
						showPossibleMoves(currentPiece.moves(puzzlePieces));
				}
			}
			else
			{
				currentPiece = e.target as Piece;
				
				if (currentPiece.currentLabel != 'rock')
					showPossibleMoves(currentPiece.moves(puzzlePieces));
			}
		}
		
		public function clearBoardSelection():void
		{
			for (var i:int = 0; i < 8; i++) 
			{
				for (var j:int = 0; j < 8; j++) 
				{
					boardSelection[i][j].gotoAndStop("unselected");
					boardSelection[i][j].removeEventListener(MouseEvent.CLICK, nextMove);
				}
			}
		}
		
		public function showPossibleMoves(moves:Array):void
		{
			var movesSize = moves.length;
			var pieceIndex:int;
			var selectionIndex:int;
			
			for( var i:int = 0; i < movesSize; i++)
			{
				if (puzzlePieces[moves[i].x][moves[i].y].currentLabel != "rock" && puzzlePieces[moves[i].x][moves[i].y].currentLabel != currentPiece.currentLabel)
				{
					if (currentPiece.currentLabel == "water" && puzzlePieces[moves[i].x][moves[i].y].currentLabel == "fire" ||
						currentPiece.currentLabel == "fire" && puzzlePieces[moves[i].x][moves[i].y].currentLabel == "water" ||
						currentPiece.currentLabel == "earth" && puzzlePieces[moves[i].x][moves[i].y].currentLabel == "wind" ||
						currentPiece.currentLabel == "wind" && puzzlePieces[moves[i].x][moves[i].y].currentLabel == "earth" ||
						puzzlePieces[moves[i].x][moves[i].y].currentLabel == "empty")
					{
						boardSelection[moves[i].x][moves[i].y].gotoAndStop("selected");
						boardSelection[moves[i].x][moves[i].y].addEventListener(MouseEvent.CLICK, nextMove, false, 0, true);
					
						pieceIndex = gameState.getChildIndex(puzzlePieces[moves[i].x][moves[i].y]);
						selectionIndex = gameState.getChildIndex(boardSelection[moves[i].x][moves[i].y]);
					  
						if(pieceIndex > selectionIndex)
							swapChildren(boardSelection[moves[i].x][moves[i].y], puzzlePieces[moves[i].x][moves[i].y]);
					}
				}
			}
		}
		
		public function nextMove(e:MouseEvent):void
		{
			clearBoardSelection();
			
			var secondPiece:Piece = puzzlePieces[e.target.row][e.target.column];
			var currentPieceLabel:String = currentPiece.currentLabel;
			var secondPieceLabel:String = secondPiece.currentLabel;
			
			
			if (secondPieceLabel == "empty")
			{
				puzzlePieces[secondPiece.row][secondPiece.column].gotoAndStop(currentPieceLabel);
				puzzlePieces[currentPiece.row][currentPiece.column].gotoAndStop(secondPieceLabel);
			}
			else if (currentPieceLabel == "fire" && secondPieceLabel == "water" || 
					currentPieceLabel == "water" && secondPieceLabel == "fire")
			{
				
				puzzlePieces[currentPiece.row][currentPiece.column].gotoAndStop("empty");
				puzzlePieces[secondPiece.row][secondPiece.column].gotoAndStop("empty");
				
			}
			else if (currentPieceLabel == "wind" && secondPieceLabel == "earth" || 
					currentPieceLabel == "earth" && secondPieceLabel == "wind")
			{
				
				puzzlePieces[currentPiece.row][currentPiece.column].gotoAndStop("empty");
				puzzlePieces[secondPiece.row][secondPiece.column].gotoAndStop("empty");
			}
			
			if (Piece.checkSolution(puzzlePieces, levelNumber))
			{
				var victory:Victory = new Victory();
				victory.x = gameState.width/2;
				victory.y = gameState.height/2;
				gameState.addChild(victory);
				
				publicTimer = new Timer(50, 100);
				publicTimer.addEventListener(TimerEvent.TIMER, alphaPuzzle, false, 0, true);
				publicTimer.addEventListener(TimerEvent.TIMER_COMPLETE, puzzleComplete, false, 0, true);
				publicTimer.start();
			}
		}
		
		public function alphaPuzzle(e:TimerEvent):void
		{
			gameState.alpha -= 0.01;
		}
		
		public function puzzleComplete(e:TimerEvent):void
		{
			publicTimer.removeEventListener(TimerEvent.TIMER, alphaPuzzle);
			publicTimer.removeEventListener(TimerEvent.TIMER_COMPLETE, puzzleComplete);
			stage.removeEventListener(KeyboardEvent.KEY_DOWN, restartPuzzle);
			gameOver();
		}
		
		public function clearContainers():void
		{
			platformObjects = new Array();
			solidObjects = new Array();
			wallObjects = new Array();
			prizeObjects = new Array();
			movingObjects = new Array();
			puzzlePieces = new Array();
			movingPlataforms = new Array();
			boardSelection = new Array();
			fireArray = new Array();
		}
		
		// addLevel é o número que desejo incrementar para mudar de fase.
		// O default é zero.
		public function selectLevel(libraryName:String="GameLevel", addLevel:int=0):void
		{
			levelNumber += addLevel;
			var levelName:String = libraryName +  levelNumber.toString();
			var className:Class = getDefinitionByName(levelName) as Class;
			
			gameState = new className();
			
			addChild(gameState);
		}
		
		public function checkLevelObjects():void
		{
			for(var i:int=0; i < gameState.numChildren; i++)
			{
				var mc:Object = gameState.getChildAt(i);
				
				if (mc is MovingPlataforms)
				{
					movingPlataforms.push(mc);
				}

				if (mc is Platform || mc is Floor)
				{
					
					platformObjects.push(mc);
				}
				else if (mc is Wall)
				{
					wallObjects.push(mc);
					solidObjects.push(mc);
				}
				else if (mc is MovingBox)
				{
					movingObjects.push(mc);
					solidObjects.push(mc);
					platformObjects.push(mc);
				}
				else if (mc is FireCollision)
				{
					fireArray.push(mc);
				}
			}
		}
		
		public function addLevelEvents():void
		{
			stage.addEventListener(Event.ENTER_FRAME, updateGame, false, 0, true);
			stage.addEventListener(KeyboardEvent.KEY_DOWN, checkKeyDown, false, 0, true);
			stage.addEventListener(KeyboardEvent.KEY_UP, checkKeyUp, false, 0, true);
		}
		
		public function removeLevelEvents():void
		{
			stage.removeEventListener(Event.ENTER_FRAME, updateGame);
			stage.removeEventListener(KeyboardEvent.KEY_DOWN, checkKeyDown);
			stage.removeEventListener(KeyboardEvent.KEY_UP, checkKeyUp);
		}
		
		public function gameOver():void
		{
			removeChild(gameState);
			gameState = new GameOver();
			addChild(gameState);
			stage.addEventListener(Event.ENTER_FRAME, scrollIntro, false, 0, true);
		}

		function checkKeyDown(e:KeyboardEvent):void
		{
			if(e.keyCode == Keyboard.LEFT)
			{
				player.moveLeft = true;
			}
			if(e.keyCode == Keyboard.UP)
			{
				if(!player.inAir){ 
					player.jump = true;
				}
			}
			if(e.keyCode == Keyboard.RIGHT)
			{
				player.moveRight = true;
			}
			if(e.keyCode == Keyboard.DOWN)
			{
				player.duck = true;
			}
		}
		
		function checkKeyUp(e:KeyboardEvent):void
		{
			if(e.keyCode == Keyboard.LEFT)
			{
				player.moveLeft = false;
			}
			if(e.keyCode == Keyboard.RIGHT)
			{
				player.moveRight = false;
			}
			if(e.keyCode == Keyboard.DOWN)
			{
				player.duck = false;
			}
		}
		public function AcionarBblueFaerieFollow(e:Event)
		{
			blueFaerie.updateFaeriePosition(player);
		}
		
		public function updateGame(e:Event):void
		{
			if (lastTime == 0) 
			{
				lastTime = getTimer();
			}
			
			for (var i:uint = 0;i<fireArray.length;i++)
			{				
			
				if (player.hitTestObject(fireArray[i]))
				{
					if(!condicao)
						startLevel();
					else
						puzzleTutorial();
				}	
					
				
			}
			
			var timeDiff:int = getTimer() - lastTime;
			lastTime += timeDiff;
			
			moveBoxes(timeDiff);
			moveCharacter(player, timeDiff);
			scrollWithHero(player);
			
			playerMask.x = player.x;
			playerMask.y = player.y;
			yellowFaerie.x = playerMask.x;
			
			yellowFaerie.updateFaeriePosition(player);
			

			if (blueFaerie.hitTestObject(player) && condicao==false)
			{
				addEventListener(Event.ENTER_FRAME, AcionarBblueFaerieFollow);
				condicao = true;
				trace(condicao);
			}
			
			for (var j : uint = 0; j < movingPlataforms.length; j++)
			{
				
				movingPlataforms[j].x += velocidade;					
				if (deslocamento >= 65)
				{						
					
					velocidade *= -1;
					deslocamento = 0;							
				}				
			}
			
			deslocamento++;
		}
		
		// Movimenta o level para direita ou esquerda.
		public function scrollWithHero(char:Player) {
			var stagePosition:Number = gameState.x + char.x;
			var rightEdge:Number = stage.stageWidth - edgeDistance;
			var leftEdge:Number = edgeDistance;
			
			if (stagePosition > rightEdge) {
				gameState.x -= (stagePosition - rightEdge);
				if (gameState.x < -(gameState.width-stage.stageWidth)) gameState.x = -(gameState.width - stage.stageWidth);
			}
			if (stagePosition < leftEdge) {
				gameState.x += (leftEdge - stagePosition);
				if (gameState.x > 0) gameState.x = 0;
			}
		}
		
		public function moveBoxes(timeDiff:Number):void
		{
			if (timeDiff < 1) return;
			
			for (var i:int = 0; i < movingObjects.length; i++)
			{
				
				var verticalChange:Number = movingObjects[i].dy * timeDiff + timeDiff * gravity;
				
				if (verticalChange > 20.0) 
				{
					verticalChange = 20.0;
				}
				
				movingObjects[i].dy += timeDiff * gravity;
				var newY:Number = movingObjects[i].y + verticalChange;
				movingObjects[i].falling = true;
				
				for (var j:uint = 0; j < platformObjects.length; j++)
				{
					if ( movingObjects[i] == platformObjects[j])
						continue;
						
					if( Collision.checkTop(movingObjects[i], platformObjects[j], newY))
					{
						if (movingObjects[i] is MovingBox && platformObjects[j] is MovingBox)  // Uma caixa em cima da outra
							platformObjects[j].bellowBox = true;
						
						newY = platformObjects[j].y - platformObjects[j].height/2 - movingObjects[i].height/2;
						movingObjects[i].dy = 0;
						movingObjects[i].bellowBox = false;						
						movingObjects[i].falling = false;
						break;
					}
				}
				
				movingObjects[i].y = newY;
			}
		}
		
		public function moveCharacter(char:MovieClip, timeDiff:Number):void
		{
			if (timeDiff < 1) return;
			
			// Personagem sendo puxado pela gravidade.
			var verticalChange:Number = char.dy * timeDiff + timeDiff * gravity;
			
			if (verticalChange > 20.0) 
			{
				verticalChange = 20.0;
			}
			
			
			
			char.dy += timeDiff * gravity;
			
			var horizontalChange = 0;
			var newAnimState:String = "stand";
			var newDirection:int = char.direction;
			
			
			if (char.moveLeft) 
			{
				horizontalChange = -char.walkSpeed * timeDiff;
				newAnimState = "walk";
				newDirection = -1;
			}
			else if (char.moveRight) 
			{
				horizontalChange = char.walkSpeed * timeDiff;
				newAnimState = "walk";
				newDirection = 1;
			}
			
			if (char.jump) 
			{
				char.jump = false;
				char.inAir = true;
				char.dy = -char.jumpSpeed;
				verticalChange = -char.jumpSpeed;
			}
			
			
			var newY:Number = char.y + verticalChange;
			var newX:Number = char.x + horizontalChange;
			
			
			char.inAir = true;
			
			// Caiu em um buraco, por exemplo.
			if ( newY > levelHeight)
			{
				startLevel();
			}
			
			// Estamos caindo, só queremos checar colisão com a plataforma ou as caixas nesse momento.
			if (newY > char.y)
			{			
				if (char.inAir)
				{
					for (var i:uint = 0; i < movingPlataforms.length; i++)
					{
						if (Collision.checkTop(char,movingPlataforms[i],newY))
						{
							newX += velocidade;
							char.inAir = false;
							break;
						}
					}
				
				}
				
				for (i = 0; i < movingObjects.length; i++)
				{
					
					if( Collision.checkTop(char, movingObjects[i], newY))
					{
						newY = Math.floor((movingObjects[i].y - movingObjects[i].height/2) - char.height/2);
						char.dy = 0;
						char.inAir = false;
						break;
					}
				}
			   	
				if (char.inAir)
				{
					for (i = 0; i < platformObjects.length; i++)
					{
						if( Collision.checkTop(char, platformObjects[i], newY))
						{
							newY = platformObjects[i].y - platformObjects[i].height/2 - char.height/2;
							char.dy = 0;
							char.inAir = false;
							break;
						}
					}
				}
				
				if (char.inAir)
				{
					for (i = 0; i < wallObjects.length; i++)
					{
						if( Collision.checkTop(char, wallObjects[i], newY))
						{
							newY = Math.floor((wallObjects[i].y - wallObjects[i].height/2) - char.height/2);
							
							char.inAir = false;
							break;
						}
					}
				}
			}
			

			
			// Checagem se o jogador está colidindo com alguma caixa
			for (i = 0; i < movingObjects.length; i++)
			{
				var newXBox:Number;
				var noCollision:Boolean = true;
				var wallPos:Number;
				
				if(Collision.checkLeftBox(char, movingObjects[i], newX, newY))
				// Se o personagem colidiu na esquerda da caixa
				{
					if (movingObjects[i].falling || movingObjects[i].collision || movingObjects[i].bellowBox)
					{
						newX = char.x;						
						char.hitWallLeft = true;
						break;
					}
					else
					{
						newXBox = newX + char.width / 2 + movingObjects[i].width/2;
						noCollision = true;
							
						for (var j:int = 0; j < solidObjects.length; j++)
						{	
							
							// Se a direita da caixa colidiu com a parede
							if (Collision.checkLeftSide(movingObjects[i], solidObjects[j], newXBox))
							{	
								noCollision = false;
								wallPos = solidObjects[j].x - solidObjects[j].width / 2;
								break;
							}
						}
						
						if (noCollision)
						{
							// O +1 é um bug de arredondamento
							movingObjects[i].x = Math.ceil(newXBox);
							
						}
						else
						{
							movingObjects[i].x = wallPos - movingObjects[i].width / 2;
							newX = wallPos - movingObjects[i].width - char.width / 2;
							break;
						}
					}
				}
				
				// Se o personagem colidiu na direita da caixa
				if( Collision.checkRightBox(char, movingObjects[i], newX, newY))
				{
					if (movingObjects[i].falling || movingObjects[i].collision || movingObjects[i].bellowBox)
					{
						newX = movingObjects[i].x + movingObjects[i].width / 2 + char.width/2;;
						
						char.hitWallRight = true;
						break;
					}
					else
					{
						newXBox = newX - char.width / 2 - movingObjects[i].width/2;
						noCollision = true;
							
						for (j = 0; j < solidObjects.length; j++)
						{	
							
							// Se a direita da caixa colidiu com a parede
							if (Collision.checkRightSide(movingObjects[i], solidObjects[j], newXBox))
							{				
								noCollision = false;
								wallPos = solidObjects[j].x + solidObjects[j].width / 2;
								break;
							}
						}
						
						if (noCollision)
						{
							
							movingObjects[i].x = Math.floor(newXBox);
						}
						else
						{
							movingObjects[i].x = wallPos + movingObjects[i].width / 2;
							newX = wallPos + movingObjects[i].width + char.width / 2;
							break;
							
						}
					}
				}
			}
			
			
			for (j = 0; j < wallObjects.length; j++)
			{
				if(Collision.checkLeftSide(char, wallObjects[j], newX))
				{
					newX = Math.floor(wallObjects[j].leftSide - char.width/2);
					
					char.hitWallLeft = true;
					break;
				}
				
				if(Collision.checkRightSide(char, wallObjects[j], newX))
				{
					newX = Math.ceil(wallObjects[j].rightSide + char.width/2);
					
					char.hitWallRight = true;
					break;
				}
			}
			
			char.x = newX;
			char.y = newY;
			
			if (char.inAir)
			{
				newAnimState = "jump";
			}
			
			char.animState = newAnimState;
			
			if (char.animState == "walk") 
			{
				char.animStep += timeDiff/60;
				
				if (char.animStep > char.walkAnimation.length)
				{
					char.animStep = 0;
				}
				
				playerMask.gotoAndStop(char.walkAnimation[Math.floor(char.animStep)]);
			} 
			else 
			{
				playerMask.gotoAndStop(char.animState);
			}
			
			if (newDirection != char.direction) 
			{
				char.direction = newDirection;
				char.scaleX = char.direction;
				
				playerMask.scaleX = char.direction;
			}	
		}

	}
	
}
